#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <mutex>
#include <queue>
#include <condition_variable>
#include <cstdlib>
#include <ctime>
#include <chrono>
#include <fstream>
#include <unordered_map>
#include <functional>

// Namespace to encapsulate the communication system
namespace CommunicationSystem {

// Simulated Message structure
struct Message {
    int id; // Message ID
    std::string content; // Message Content
    bool requiresAck; // Whether acknowledgment is required

    Message(int id, const std::string& content, bool ack) : id(id), content(content), requiresAck(ack) {}
};

// Message Queue class for thread-safe operations
class MessageQueue {
private:
    std::queue<Message> queue;
    std::mutex mtx;
    std::condition_variable cv;

public:
    void push(const Message& message) {
        std::lock_guard<std::mutex> lock(mtx);
        queue.push(message);
        cv.notify_one();
    }

    Message pop() {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, [this] { return !queue.empty(); });
        Message msg = queue.front();
        queue.pop();
        return msg;
    }

    bool empty() {
        std::lock_guard<std::mutex> lock(mtx);
        return queue.empty();
    }
};

// Logger class for logging events
class Logger {
private:
    std::ofstream logFile;
    std::mutex logMutex;

public:
    Logger(const std::string& filename) {
        logFile.open(filename, std::ios::out | std::ios::app);
        if (!logFile.is_open()) {
            throw std::runtime_error("Failed to open log file");
        }
    }

    ~Logger() {
        if (logFile.is_open()) {
            logFile.close();
        }
    }

    void log(const std::string& message) {
        std::lock_guard<std::mutex> lock(logMutex);
        logFile << std::chrono::system_clock::to_time_t(std::chrono::system_clock::now())
                << " - " << message << std::endl;
    }
};

// Communication Node class (Sender or Receiver)
class Node {
private:
    std::string name;
    MessageQueue& inboundQueue;
    MessageQueue& outboundQueue;
    bool running;
    std::thread workerThread;
    Logger& logger;
    std::unordered_map<int, std::chrono::steady_clock::time_point> sentMessages;
    std::mutex msgMutex;

    void processMessages() {
        while (running) {
            if (!inboundQueue.empty()) {
                Message msg = inboundQueue.pop();
                logger.log(name + " received message ID " + std::to_string(msg.id) + ": " + msg.content);
                std::cout << name << " received message ID " << msg.id << ": " << msg.content << std::endl;

                if (msg.requiresAck) {
                    sendMessage(Message(msg.id, "ACK for message " + std::to_string(msg.id), false));
                } else {
                    std::lock_guard<std::mutex> lock(msgMutex);
                    sentMessages.erase(msg.id);
                }
            }
            checkForTimeouts();
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }
    }

    void checkForTimeouts() {
        std::lock_guard<std::mutex> lock(msgMutex);
        auto now = std::chrono::steady_clock::now();
        for (auto it = sentMessages.begin(); it != sentMessages.end();) {
            if (std::chrono::duration_cast<std::chrono::seconds>(now - it->second).count() > 2) {
                logger.log(name + " retrying message ID " + std::to_string(it->first));
                std::cout << name << " retrying message ID " << it->first << std::endl;
                sendMessage(Message(it->first, "Retransmitted message " + std::to_string(it->first), true));
                it->second = now;
            }
            ++it;
        }
    }

public:
    Node(const std::string& nodeName, MessageQueue& inQueue, MessageQueue& outQueue, Logger& log)
        : name(nodeName), inboundQueue(inQueue), outboundQueue(outQueue), logger(log), running(true) {
        workerThread = std::thread(&Node::processMessages, this);
    }

    ~Node() {
        running = false;
        if (workerThread.joinable()) {
            workerThread.join();
        }
    }

    void sendMessage(const Message& message) {
        {
            std::lock_guard<std::mutex> lock(msgMutex);
            sentMessages[message.id] = std::chrono::steady_clock::now();
        }
        outboundQueue.push(message);
        logger.log(name + " sent message ID " + std::to_string(message.id) + ": " + message.content);
        std::cout << name << " sent message ID " << message.id << ": " << message.content << std::endl;
    }
};

// Simulated Communication System
class CommunicationSystem {
private:
    MessageQueue queueAtoB;
    MessageQueue queueBtoA;
    Logger logger;
    Node nodeA;
    Node nodeB;

public:
    CommunicationSystem()
        : logger("communication.log"),
          nodeA("Node A", queueBtoA, queueAtoB, logger),
          nodeB("Node B", queueAtoB, queueBtoA, logger) {}

    void simulate() {
        // Simulating messages
        for (int i = 1; i <= 10; ++i) {
            nodeA.sendMessage(Message(i, "Hello from A to B", true));
            nodeB.sendMessage(Message(i + 10, "Hello from B to A", true));
            std::this_thread::sleep_for(std::chrono::milliseconds(500));
        }
    }
};

} // namespace CommunicationSystem

int main() {
    // Seed random number generator
    std::srand(std::time(nullptr));

    // Instantiate and simulate the communication system
    CommunicationSystem::CommunicationSystem system;
    system.simulate();

    return 0;
}
